/*
    Questa classe contiene tutti gli oggetti ed i metodi del simulatore
    Una eventuale console, o direttamente l'interfaccia grafica,
    interagisce esclusivamente con questa classe
 */


using namespace std;

class Simulator {
private:
    ASs vettoreAS;
    PhysicalNet reteFisica;
    LogicalNet reteLogica;
    TrafficNet traffico;
	SimulateAttack attacco;

public:
    Simulator();
    Simulator(int n);
    bool physicalConfig(); // legge il file di configurazione ed istanzia AS e link fisici
    bool trafficConfig(); // legge il file di configurazione ed istanzia il traffico
    //bool trafficUpdate();
    bool saveConfig(string filename); // salva la configurazione corrente (AS, link fisici e traffico) nel file di configurazione
    bool addAS(int number, string name, string owner, bool state);
    bool addLink(int i, int j, string policy, int bandwidth, int latency, bool state);
    bool calculateLogicalNet();
    bool setTraffic(int i, int j, string key, int value, bool state);
    bool setTraffic(int i, int j, int value1, int value2, int value3, bool state);
    bool addTraffic(int i, int j, string key, int value);
    bool removeTraffic(int i, int j, string key, int value);
    bool deleteTraffic(int i, int j); //tutto il traffico da i a j
    bool deleteTraffic(int i, int j, string key); //solo il traffico in un tipo
    int getBandwidthUsageLink(int i, int j);
    bool checkLink(int i, int j);
    bool checkBandwidth();
    bool killAS(int number);
	time_t startDOSAttack( int ASOrigin, int ASVictim, int amount );
    bool update();
    bool checkTraffic(int i, int j);

string getPath(int i, int j);
bool printXml();

//@simone dovresti implementare questo
bool deleteAs( int i ); //per eliminare un as

//@simone dovresti implementare questo
bool deleteLink( int i ,int j );

//@simone dovresti implementare questo
bool linkPhysicalAttack( int i,int j );


    //funzioni di stampa temporanee, dopo si laveno
    void printASs();
    void printPhysicalNet();
    void printLogicalNet();
    void printASPath(int i, int j);
    void printASPaths();
    void printTraffics();
};

Simulator::Simulator() {
    ;
}

Simulator::Simulator(int n) {
    vettoreAS.resize(n);
    vettoreAS.init();
    reteFisica.resize(n);
    reteFisica.init();
    reteLogica.resize(n);
    reteLogica.init();
    traffico.resize(n);
    traffico.init();
}

// prende in input il file di configurazione config.xml che descrive la rete fisica ed
// istanzia i relativi oggetti del simulatore (vettoreAS e reteFisica).
bool Simulator::physicalConfig() {
    string file = "config.xml";
    TiXmlDocument doc(file.c_str());

    // Parsing
    if (doc.LoadFile()) {
        TiXmlElement *root = doc.RootElement();

        // loops over the config file
        for (TiXmlElement* item = root->FirstChildElement(); item; item = item->NextSiblingElement()) {
            if (item->ValueStr().compare("AS") == 0) { // Add AS
                vettoreAS.addAS(atoi(item->Attribute("number")), item->Attribute("name"), item->Attribute("owner"), item->Attribute("state"));
            }
            else if (item->ValueStr().compare("PhysicalLink") == 0) { // Add physical link
                reteFisica.addLink(atoi(item->Attribute("ASOrigin")), atoi(item->Attribute("ASDestination")), item->Attribute("policy"), atoi(item->Attribute("bandwith")), atoi(item->Attribute("latency")), item->Attribute("state"));
            }
        }
    }
}

// prende in input il file config.xml che descrive le rischieste di traffico tra i vari AS
// ed istanzia i relativi oggetti del simulatore (traffico).
bool Simulator::trafficConfig() {
    string file = "config.xml";
    TiXmlDocument doc(file.c_str());

    // Parsing
    if (doc.LoadFile()) {
        TiXmlElement *root = doc.RootElement();

        // loops over the config file
        for (TiXmlElement* item = root->FirstChildElement(); item; item = item->NextSiblingElement()) {
            if (item->ValueStr().compare("Traffic") == 0) { // Add AS
                setTraffic(atoi(item->Attribute("ASOrigin")), atoi(item->Attribute("ASDestination")), "web", atoi(item->Attribute("web")), item->Attribute("state"));
                setTraffic(atoi(item->Attribute("ASOrigin")), atoi(item->Attribute("ASDestination")), "voip", atoi(item->Attribute("voip")), item->Attribute("state"));
                setTraffic(atoi(item->Attribute("ASOrigin")), atoi(item->Attribute("ASDestination")), "p2p", atoi(item->Attribute("p2p")), item->Attribute("state"));
            }
        }
    }
}

bool Simulator::saveConfig(string filename) {
    TiXmlDocument doc;
    TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "UTF-8", "");

    doc.LinkEndChild(decl); // Inserisci la dichiarazione

    // Inserisci la radice
    TiXmlElement *root = new TiXmlElement("Net");
    doc.LinkEndChild(root);

    // Inserisci tutti gli AS
    for (int i = 1; i < vettoreAS.getSize(); i++) {
        TiXmlElement *AS = new TiXmlElement("AS");
        AS->SetAttribute("number", i);
        AS->SetAttribute("name", vettoreAS.getName(i));
        AS->SetAttribute("owner", vettoreAS.getOwner(i));
        AS->SetAttribute("state", vettoreAS.getState(i));

        root->LinkEndChild(AS);
    }

    // Inserisci tutti i link fisici
    for (int i = 1; i < reteFisica.getSize(); i++) {
        for (int j = 1; j < reteFisica.getSize(); j++) {
            if ((reteFisica.getPolicy(i, j)).empty())
                continue;

            TiXmlElement *link = new TiXmlElement("PhysicalLink");
            link->SetAttribute("ASOrigin", i);
            link->SetAttribute("ASDestination", j);
            link->SetAttribute("policy", reteFisica.getPolicy(i, j));
            link->SetAttribute("bandwith", reteFisica.getBandwidth(i, j));
            link->SetAttribute("latency", reteFisica.getLatency(i, j));
            link->SetAttribute("state", reteFisica.getState(i, j));

            root->LinkEndChild(link);
        }
    }

    // Inserisci il traffico presente nella rete
    for (int i = 1; i < traffico.getSize(); i++) {
        for (int j = 1; j < traffico.getSize(); j++) {
            if(traffico.getState(i, j)==0)
                continue;

            TiXmlElement *traffic = new TiXmlElement("Traffic");
            traffic->SetAttribute("ASOrigin", i);
            traffic->SetAttribute("ASDestination", j);
            traffic->SetAttribute("web", traffico.getTraffic(i, j, "web"));
            traffic->SetAttribute("voip", traffico.getTraffic(i, j, "voip"));
            traffic->SetAttribute("p2p", traffico.getTraffic(i, j, "p2p"));
            traffic->SetAttribute("state", traffico.getState(i, j));

            root->LinkEndChild(traffic);
        }
    }

    doc.SaveFile(filename);
}

// aggiunge un AS al vottore degli AS
bool Simulator::addAS(int i, string name, string owner, bool state) {
    if ( vettoreAS.getSize() <= i )
    {
        vettoreAS.resize(i+1);
        reteFisica.resize(i+1);
        reteLogica.resize(i+1);
        traffico.resize(i+1);
    }
    vettoreAS.addAS(i, name, owner, state);
    cout << "dimensione vettore AS: " << vettoreAS.getSize() << endl;
}

// aggiunge un link fisico alla matrice dei link fisici
bool Simulator::addLink(int i, int j, string policy, int bandwidth, int latency, bool state) {
    reteFisica.addLink(i, j, policy, bandwidth, latency, state);
}

// costruisce la rete logica a partire dalla rete fisica
bool Simulator::calculateLogicalNet() {
    reteLogica.calculateASPaths(&reteFisica);
    reteLogica.updateNet(&reteFisica);
}

// imposta il traffico di un certo tipo tra 2 AS
bool Simulator::setTraffic(int i, int j, string key, int value, bool state) {
    traffico.setTraffic(i, j, key, value, state);
    list < pair<int, int> > lista = reteLogica.getASPathPair(i, j);
    list < pair<int, int> >::iterator it;
    for (it = lista.begin(); it != lista.end(); it++) {
        //cout << "consumo banda " << value << " dal link fisico (" <<(*it).first<<"-"<<(*it).second<<") per il traffico "<<i<<"-"<<j<<endl;
        reteFisica.addBandwidthUsage((*it).first, (*it).second, value);
    }
}

// imposta tutti i traffici tra 2 AS
bool Simulator::setTraffic(int i, int j, int value1, int value2, int value3, bool state) {
    setTraffic(i, j, "web", value1, state);
    setTraffic(i, j, "voip", value2, state);
    setTraffic(i, j, "p2p", value3, state);
}

// aggiunge traffico di un certo tipo tra 2 AS, andando ad aggiungersi al traffico gia presente
bool Simulator::addTraffic(int i, int j, string key, int value) {
    traffico.addTraffic(i, j, key, value);
}

// rimuove una certa quatità di traffico di un certo tipo tra 2 AS
bool Simulator::removeTraffic(int i, int j, string key, int value) {
    traffico.removeTraffic(i, j, key, value);
}

// elimina tutto il traffico tra 2 AS
bool Simulator::deleteTraffic(int i, int j) {
    traffico.setTraffic(i, j, "web", 0, false);
    traffico.setTraffic(i, j, "voip", 0, false);
    traffico.setTraffic(i, j, "p2p", 0, false);
}

// elimina tutto il traffico di un certo tipo tra 2 AS
bool Simulator::deleteTraffic(int i, int j, string key) {
    traffico.setTraffic(i, j, key, 0, true); //da correggere il true, non dovrebbe passare nulla
}

// controlla che il traffico tra 2 AS sia soddisfatto (banda sufficiente?, latenza accettabile?)
bool Simulator::checkTraffic(int i, int j)
{
    //list < pair<int, int> > ASPath = reteLogica.getASPathPair(i, j);
    //cout << "controllo traffico da "<<i<<" a "<<j<<" : ";
    int bandwidth = reteLogica.getBandwidth(i,j);
    int traffic = traffico.getTraffic(i,j);
    //cout << "banda = "<<bandwidth<<" , traffico = "<<traffic<<" . ";
    if (traffic <= bandwidth)
    {
        //cout << "OK";
        return true;
    }
    else
    {
        //cout << "non basta";
        return false;
    }
}

// restituisce il totale della banda passante per un certo link fisico
int Simulator::getBandwidthUsageLink(int i, int j) {
    int somma = 0;
    list < pair<int, int> > lista = reteLogica.getASPathsThroughLink(i, j);
    list < pair<int, int> >::iterator it;
    for (it = lista.begin(); it != lista.end(); it++) {
        somma = somma + traffico.getTraffic((*it).first, (*it).second);
        //cout << "aggiunta banda "<<traffico.getTraffic(origin,destination)<<" da "<<origin<<"-"<<destination<<endl;
    }
    return somma;
}

// controlla se sul link dato in input la banda passante è superiore alla capacità di banda
bool Simulator::checkLink(int i, int j) {
    int bandwidthCapacity = reteFisica.getBandwidth(i, j);
    int bandwidthActive = getBandwidthUsageLink(i, j);
    //cout << "link " << i << "-" << j << ": capacità: " << bandwidthCapacity << "\t, passante: " << bandwidthActive << "\t, parziale: " << reteFisica.getBandwidthUsage(i,j) + reteFisica.getBandwidthUsage(j,i) << "\t => ";
    if (getBandwidthUsageLink(i, j) <= bandwidthCapacity) {
        //cout << "OK" << endl;
        return true;
    } else {
        //cout << "Banda insufficiente" << endl;
        return false;
    }
}

// controlla se su ogni link della rete fisica, la banda passante è superiore alla capacità di banda
bool Simulator::checkBandwidth() {
    //cout << "check della banda: " << endl;
    bool flag = true;
    int n = vettoreAS.getSize();
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            if (reteFisica.getState(i, j) && i < j) // i<j per controllare ogni link una sola volta
                if (!checkLink(i, j))
                    flag = false;
    return flag;
    //cout << endl;
}

// disattiva un AS e tutti i relativi link fisici
bool Simulator::killAS(int number) {
    vettoreAS.setState(number, false);
    reteFisica.killLinksWithAS(number);
    list< pair<int,int> > ASPathsThroughAS = reteLogica.getASPathsThroughAS(number);
    //traffico.killLinksWithAS(number);
    //traffico.killLinks(ASPathsThroughAS);
}

time_t Simulator::startDOSAttack( int ASOrigin, int ASVictim, int amount ) {
	time_t timeOfAttack;
	timeOfAttack = attacco.StartDOSAttack(ASOrigin, ASVictim, amount, &traffico );
	update();
}

bool Simulator::update() {
    reteFisica.reset();
    reteLogica.reset();
    calculateLogicalNet();
    //trafficUpdate();
}

////////////////////////////////////////////////////////////////////////////////////
// FUNZIONI DI STAMPA TEMPORANEE, UTILI PER FARE TEST MA ALLA FINE SE NE ANDRANNO //
////////////////////////////////////////////////////////////////////////////////////

void Simulator::printASs() {
    cout << "list of all the Autonomous Systems: " << endl;
    vettoreAS.print();
    cout << endl;
}

void Simulator::printPhysicalNet() {
    int n = vettoreAS.getSize();
    cout << "Stampa della matrice fisica (banda/delay) :" << endl;

    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
        {
            if (!vettoreAS.getState(i) && i!=0)
            {}
            else if (!vettoreAS.getState(j) && j!=0)
            {}
            else if (i == 0 && j == 0)
                cout << endl;
            else {
                if (j == 0 && i != 0)
                    cout << vettoreAS.getName(i);

                if (i == 0 && j != 0)
                    cout << "\t" << vettoreAS.getName(j);

                if (i != 0 && j != 0) {
                    if (reteFisica.getState(i, j))
                        cout << "\t" << reteFisica.getBandwidth(i, j) << "/" << reteFisica.getLatency(i, j);
                    else
                        cout << "\t" << "x";
                }

                if (j == n - 1)
                    cout << endl;
                if (i == 0 && j == n - 1)
                    cout << endl;
            }
        }
    cout << endl;
}

void Simulator::printLogicalNet() {
    int n = vettoreAS.getSize();
    cout << "Stampa della matrice logica (banda/delay) :" << endl;

    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
        {
            if (!vettoreAS.getState(i) && i!=0)
            {}
            else if (!vettoreAS.getState(j) && j!=0)
            {}

            else if (i == 0 && j == 0)
                cout << endl;
            else {
                if (j == 0 && i != 0)
                    cout << vettoreAS.getName(i);

                if (i == 0 && j != 0)
                    cout << "\t" << vettoreAS.getName(j);

                if (i != 0 && j != 0) {
                    if (reteLogica.getState(i, j))
                        cout << "\t" << reteLogica.getBandwidth(i, j) << "/" << reteLogica.getLatency(i, j);
                    else
                        cout << "\t" << "x";
                }

                if (j == n - 1)
                    cout << endl;
                if (i == 0 && j == n - 1)
                    cout << endl;
            }
        }
    cout << endl;
}

void Simulator::printASPath(int i, int j) {
    list<int> lista = reteLogica.getASPath(i, j);
    list<int>::iterator it;
    cout << "ASPath da " << vettoreAS.getName(i) << " a " << vettoreAS.getName(j) << " :";
    for (it = lista.begin(); it != lista.end(); it++)
        cout << " " << vettoreAS.getName(*it);
    cout << endl;
}

void Simulator::printASPaths() {
    int n = vettoreAS.getSize();
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++) {
            if (reteLogica.getState(i, j)) {
                printASPath(i, j);
            }
        }
    cout << endl;
}

void Simulator::printTraffics() {
    int n = traffico.getSize();
    cout << "Stampa della matrice del traffico (web/voip/p2p) :" << endl;

    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
        {
            if (!vettoreAS.getState(i) && i!=0)
            {}
            else if (!vettoreAS.getState(j) && j!=0)
            {}

            else if (i == 0 && j == 0)
                cout << endl;
            else {
                if (j == 0 && i != 0)
                    cout << vettoreAS.getName(i);

                if (i == 0 && j != 0)
                    cout << "\t" << vettoreAS.getName(j);

                if (i != 0 && j != 0) {
                    if (traffico.getState(i, j))
                        cout << "\t" << traffico.getTraffic(i, j, "web") << "/" << traffico.getTraffic(i, j, "voip") << "/" << traffico.getTraffic(i, j, "p2p");
                    else
                        cout << "\t" << "x";
                }

                if (j == n - 1)
                    cout << endl;
                if (i == 0 && j == n - 1)
                    cout << endl;
            }
        }
    cout << endl;
}









bool Simulator::printXml() {
    TiXmlDocument doc;
    TiXmlDeclaration *decl = new TiXmlDeclaration("1.0", "UTF-8", "");

    doc.LinkEndChild(decl); // Inserisci la dichiarazione

    // Inserisci la radice
    TiXmlElement *root = new TiXmlElement("root");
    doc.LinkEndChild(root);

    TiXmlElement* listAS = new TiXmlElement("listAS");
    root->LinkEndChild( listAS );

    // Inserisci tutti gli AS
    for (int i = 1; i < vettoreAS.getSize(); i++) {
        TiXmlElement *AS = new TiXmlElement("item");
        AS->SetAttribute("id", i);
//        AS->SetAttribute("name", vettoreAS.getName(i));
        AS->SetAttribute("owner", vettoreAS.getOwner(i));
        AS->SetAttribute("state", vettoreAS.getState(i));

        listAS->LinkEndChild(AS);
    }



    TiXmlElement* networkPhysical = new TiXmlElement("networkPhysical");
    root->LinkEndChild( networkPhysical );

    // Inserisci tutti i link fisici
    for (int i = 1; i < reteFisica.getSize(); i++) {
        for (int j = 1; j < reteFisica.getSize(); j++) {
            if ((reteFisica.getPolicy(i, j)).empty())
                continue;

            TiXmlElement *link = new TiXmlElement("item");
            link->SetAttribute("src", i);
            link->SetAttribute("dst", j);
            link->SetAttribute("policy", reteFisica.getPolicy(i, j));
            link->SetAttribute("bandwith", reteFisica.getBandwidth(i, j));
            link->SetAttribute("bandwithUsed", reteFisica.getBandwidthUsage(i, j));
            link->SetAttribute("latency", reteFisica.getLatency(i, j));
            link->SetAttribute("state", reteFisica.getState(i, j));

            networkPhysical->LinkEndChild(link);
        }
    }




    TiXmlElement* traffics = new TiXmlElement( "traffic" );
    root->LinkEndChild( traffics );

    // Inserisci il traffico presente nella rete
    for (int i = 1; i < traffico.getSize(); i++) {
        for (int j = 1; j < traffico.getSize(); j++) {
            if(traffico.getState(i, j)==0)
                continue;

            TiXmlElement *traffic = new TiXmlElement("item");
            traffic->SetAttribute("src", i);
            traffic->SetAttribute("dst", j);
            traffic->SetAttribute("web", traffico.getTraffic(i, j, "web"));
            traffic->SetAttribute("voip", traffico.getTraffic(i, j, "voip"));
            traffic->SetAttribute("p2p", traffico.getTraffic(i, j, "p2p"));
            traffic->SetAttribute("state", traffico.getState(i, j));

//!!!
            traffic->SetAttribute("bandwith", reteLogica.getBandwidth(i, j));
            traffic->SetAttribute("latency", reteLogica.getLatency(i, j));
            traffic->SetAttribute("state", reteLogica.getState(i, j));
            traffic->SetAttribute("path",getPath( i,j ) );

            traffic->SetAttribute("satisfied",checkTraffic(i,j) );


            traffics->LinkEndChild(traffic);
        }
    }



    TiXmlElement * networkLogical = new TiXmlElement( "networkLogical" );
    root->LinkEndChild( networkLogical );
    // Inserisci tutti i link fisici
    for (int i = 1; i < reteLogica.getSize(); i++) {
        for (int j = 1; j < reteLogica.getSize(); j++) {

            TiXmlElement *link = new TiXmlElement("item");
            link->SetAttribute("src", i);
            link->SetAttribute("dst", j);
            link->SetAttribute("bandwith", reteLogica.getBandwidth(i, j));
            link->SetAttribute("latency", reteLogica.getLatency(i, j));
            link->SetAttribute("state", reteLogica.getState(i, j));
            link->SetAttribute("path",getPath( i,j ) );
            networkLogical->LinkEndChild(link);
        }
    }



    doc.SaveFile( stdout );
}



string Simulator::getPath(int i, int j){
    stringstream sstr;
    list<int> lista = reteLogica.getASPath(i, j);
    list<int>::iterator it;

    for (it = lista.begin(); it != lista.end(); it++)
        sstr <<*it <<" "  ;

    return( sstr.str() );
}


//per eliminare un as dalla rete
//@simone dovresti implementare questo
bool Simulator::deleteAs( int i )
{;}


//per eliminare un link fisico dalla rete
//@simone dovresti implementare questo
bool Simulator::deleteLink( int i ,int j )
{;}


//attacco ad un link fisico, entrambi le direzioni non devono essere capaci di traspotare
//@simone dovresti implementare questo
bool Simulator:: linkPhysicalAttack(  int i,int j )
{;}
